﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.RuleEngine;
using System.IO;
using BizUnit;
using System.Runtime.Serialization.Formatters.Binary;

namespace WhiteboardWorks.BizUnitSteps
{
    /// <summary>
    /// The BREExecuteStep can be used to submit content to the MS Business Rule Engine and optionally validate the output
    /// </summary>
    /// 
    /// <remarks>
    /// The following shows an example of the Xml representation of this test step.
    /// 
    /// <code escaped="true">
    /// 
    ///<TestStep assemblyPath="WhiteboardWorks.BizUnitSteps.dll" typeName=" WhiteboardWorks.BizUnitSteps.BREExecuteStep">
    ///  <FactsContextKey>Facts</FactsContextKey> <!-- Optional Configuration-->
    ///  <PolicyName>ResolveEndpoint_XYZ</PolicyName>
    ///  <PolicyVersionMajorRevision>1</PolicyVersionMajorRevision>
    ///  <PolicyVersionMinorRevision>0</PolicyVersionMinorRevision>

    ///  <ContextLoaderStep assemblyPath="WhiteboardWorks.BizUnitSteps.dll" typeName="WhiteboardWorks.BizUnitSteps.ESBToolkitResolutionContextLoader">
    ///    <FactsContextKey>Facts</FactsContextKey>
    ///    <DocumentType>Microsoft.Practices.ESB.ResolveProviderMessage</DocumentType>
    ///    <SourceDocument>SourceDocument.xml</SourceDocument>
    ///  </ContextLoaderStep>

    ///  <ValidationStep assemblyPath="WhiteboardWorks.BizUnitSteps.dll" typeName="WhiteboardWorks.BizUnitSteps.ESBToolkitResolutionValidationStep">
    ///    <FactsContextKey>Facts</FactsContextKey>
    ///    <TransportType>WCF-SQL</TransportType>
    ///    <TransportLocation>http://databaseXYZ/ABC</TransportLocation>
    ///    <Action>SubmitMessage</Action>
    ///    <TargetNamespace>http://abc/xyz</TargetNamespace>
    ///  </ValidationStep>
      
    ///</TestStep>
    ///
    ///	</code>
    ///	
    ///	<list type="table">
    ///		<listheader>
    ///			<term>FactsContextKey</term>
    ///			<description>Optional parameter to be filled if additional facts other than the source document need to be submitted to the Business Rule Engine. If this parameter is supplied the BizUnit Step will try to load facts from the BizUnit Context using this key and forward them to the BRE.</description>
    ///		</listheader>
    ///		<item>
    ///			<term>PolicyName</term>
    ///			<description>The policy name to be executed by the unit test step. This parameter is required.</description>
    ///		</item>
    ///		<item>
    ///			<term>PolicyVersionMajorRevision</term>
    ///			<description>The policy version major revision, as an example if the policy version to be executed is 1.0, the value of this field should be set to 1. If this parameter is not supplied the unit test step will execute the latest version of the deployed policy.</description>
    ///		</item>
    ///		<item>
    ///            <term>PolicyVersionMinorRevision</term>
    ///			<description>The policy version minor revision, as an example if the policy version to be executed is 1.0, the value of this field should be set to 0. This parameter is only required when PolicyVersionMajorRevision is supplied.</description>
    ///		</item>
    ///		<item>
    ///			<term>ContextLoaderSteps</term>
    ///			<description>Allows additional information to be pre-loaded into the BizUnit context.
    ///			As an example of utilization, ESBToolkitResolutionContextLoader could be used to load
    ///			additional facts to be sent to the BRE as part of the unit test. See FactsContextKey property
    ///			to get more details on how ContextLoaderSteps and ValidationSteps can be used as a complement
    ///			of this unit test step.
    ///			<para>(Optional)(zero or more)</para></description>
    ///		</item>
    ///		<item>
    ///			<term>ValidationSteps</term>
    ///			<description>Allows the inclusion of validation points against the results from the pre-configured Business Rule Execution.
    ///			<para>(Optional)(zero or more)</para></description>
    ///		</item>
    ///	</list>
    ///	</remarks>	
    public class BREExecuteStep : ITestStep
    {
        private object[] ExecutePolicy(string policyName, int majorRevision, int minorRevision, object[] facts)
        {
            Policy policy = null;

            try
            {
                if (majorRevision > 0)
                {
                    policy = new Policy(policyName);
                }
                else
                {
                    policy = new Policy(policyName, majorRevision, minorRevision);
                }
                policy.Execute(facts);
            }
            catch(Exception ex)
            {
                throw new ApplicationException("BREExecuteStep could not execute the policy provided. Details: " + ex.Message);
            }
            finally
            {
                if (policy!=null)
                {
                    policy.Dispose();
                }
            }

            return facts;
        }

        /// <summary>
        /// ITestStep.Execute() implementation
        /// </summary>
        /// <param name='testConfig'>The Xml fragment containing the configuration for this test step</param>
        /// <param name='context'>The context for the test, this holds state that is passed between tests</param>
        public void Execute(XmlNode testConfig, Context context)
        {
            context.LogInfo("BREExecuteStep [Start]");
 
            //Facts to be passed to the BRE
            object[] facts = null;

            //Source document
            XmlDocument xmlSourceDocument = null;

            #region Configuration

            // Read test config
            string documentType = context.ReadConfigAsString(testConfig, "DocumentType", true);
            string sourceDocument = context.ReadConfigAsString(testConfig, "SourceDocument", true);
            string factsContextKey = context.ReadConfigAsString(testConfig, "FactsContextKey", true);
            string policyName = context.ReadConfigAsString(testConfig, "PolicyName", true);
            int policyVersionMinorRevision = context.ReadConfigAsInt32(testConfig, "PolicyVersionMinorRevision", true);
            int policyVersionMajorRevision = context.ReadConfigAsInt32(testConfig, "PolicyVersionMajorRevision", true);

            //Validation config
            XmlNode validationConfig = testConfig.SelectSingleNode("ValidationStep");

            //Context Loader config
            XmlNode contextLoaderConfig = testConfig.SelectSingleNode("ContextLoaderStep");

            #endregion

            #region Input Parameters Validation

            //If neither FactsContextKey nor SourceDocument is provided then throw exception
            if (String.IsNullOrEmpty(factsContextKey) && String.IsNullOrEmpty(sourceDocument)) { throw new ApplicationException("BREExecuteStep could not obtain the source document. Make sure the test case is properly configured, if the property 'FactsContextKey' is not set, a source document must be provided. Missing Property Name: 'SourceDocument'."); }

            //If source document is provided and Document Type is not provided then throw exception
            if (!String.IsNullOrEmpty(sourceDocument) && String.IsNullOrEmpty(documentType)) { throw new ApplicationException("BREExecuteStep could not obtain the source document. Make sure the test case is properly configured, if source document is provided the document type must be set. Property Name: 'DocumentType'."); }

            if (String.IsNullOrEmpty(policyName)) { throw new ApplicationException("BREExecuteStep could not obtain the policyName. Make sure the test case is properly configured. Missing Property Name: 'PolicyName'."); }
            
            #endregion

            context.LogInfo("BREExecuteStep [Executing ContextLoaders]");

            //First step execute all the configured context loaders  (if any)
            context.ExecuteContextLoader(null, contextLoaderConfig);


            //If Facts Context Key was set then try to get facts from the Context
            if (!String.IsNullOrEmpty(factsContextKey))
            { 
                //Load facts from context
                facts = (object[])context.GetObject(factsContextKey);

                if (facts == null)
                {
                    throw new ApplicationException("BREExecuteStep could not obtain the facts from the Context. Please make sure the 'FactsContextKey' property is properly configured.");
                }
            }
            else
            {
                try
                {
                    xmlSourceDocument = new XmlDocument();
                    xmlSourceDocument.Load(sourceDocument);

                    TypedXmlDocument typedXmlDocument = new TypedXmlDocument(documentType, xmlSourceDocument);

                    facts = new object[1];
                    facts[0] = typedXmlDocument;
                 }
                 catch (Exception ex)
                 {
                    throw new ApplicationException("BREExecuteStep could not load the source document. Details: " + ex.Message);
                 }
            }

            //Execute BRE Policy
            ExecutePolicy(policyName, policyVersionMajorRevision, policyVersionMinorRevision, facts);

            //If a source document was provided pass the same as input stream to the validation steps
            if (!String.IsNullOrEmpty(sourceDocument))
            {
                MemoryStream sourceDocumentMemoryStream = new MemoryStream();
                xmlSourceDocument.Save(sourceDocumentMemoryStream);

                context.ExecuteValidator(sourceDocumentMemoryStream, validationConfig);
            }
            //Try to find an input document in the facts list
            else if (facts != null)
            {
                MemoryStream sourceDocumentMemoryStream = null;

                foreach (object fact in facts)
                {
                    if (fact is TypedXmlDocument)
                    {
                        context.LogInfo("BREExecuteStep [Input document extracted from Context Facts will be handed to Validators]");

                        TypedXmlDocument typedXmlDocument = fact as TypedXmlDocument;
                        sourceDocumentMemoryStream = new MemoryStream();

                        if (typedXmlDocument.Document.OwnerDocument != null)
                        {
                            typedXmlDocument.Document.OwnerDocument.Save(sourceDocumentMemoryStream);
                        }
                        else
                        {
                            XmlDocument factInputXmlDocument = new XmlDocument();
                            factInputXmlDocument.LoadXml(typedXmlDocument.Document.OuterXml);
                            factInputXmlDocument.Save(sourceDocumentMemoryStream);
                        }

                        break;
                    }
                }

                context.LogInfo("BREExecuteStep [Executing Validators]");
                context.ExecuteValidator(sourceDocumentMemoryStream, validationConfig);
            }
            else
            {
                context.LogInfo("BREExecuteStep [Executing Validators]");

                //If facts were loaded from Context they are still there and can be used for further validation
                context.ExecuteValidator(null, validationConfig);
            }

            context.LogInfo("BREExecuteStep [End]");
        }
    }
}
